//! Data models for the Rust GraphQL application
//! 
//! This module contains all the data structures used for representing
//! database metadata and GraphQL schema information.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

pub mod entities;

/// Information about a database table
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TableInfo {
    /// Table name
    pub name: String,
    /// Schema name (e.g., "public" in PostgreSQL)
    pub schema: String,
    /// Table comment/description
    pub comment: Option<String>,
    /// Table columns
    pub columns: Vec<ColumnInfo>,
    /// Primary key columns
    pub primary_keys: Vec<String>,
    /// Foreign key relationships
    pub foreign_keys: Vec<ForeignKeyInfo>,
}

/// Information about a database column
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ColumnInfo {
    /// Column name
    pub name: String,
    /// SQL data type
    pub data_type: String,
    /// Whether the column can be null
    pub is_nullable: bool,
    /// Whether this is a primary key column
    pub is_primary_key: bool,
    /// Whether this is an auto-increment column
    pub is_auto_increment: bool,
    /// Default value if any
    pub default_value: Option<String>,
    /// Column comment/description
    pub comment: Option<String>,
    /// Maximum length for string types
    pub max_length: Option<i32>,
    /// Precision for numeric types
    pub precision: Option<i32>,
    /// Scale for decimal types
    pub scale: Option<i32>,
}

/// Information about foreign key relationships
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ForeignKeyInfo {
    /// Name of the foreign key constraint
    pub constraint_name: String,
    /// Source table name
    pub from_table: String,
    /// Source column name
    pub from_column: String,
    /// Target table name
    pub to_table: String,
    /// Target column name
    pub to_column: String,
    /// What happens on delete (CASCADE, RESTRICT, etc.)
    pub on_delete: Option<String>,
    /// What happens on update (CASCADE, RESTRICT, etc.)
    pub on_update: Option<String>,
}

/// Type of relationship between tables
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum RelationshipType {
    /// One-to-one relationship
    OneToOne,
    /// One-to-many relationship
    OneToMany,
    /// Many-to-one relationship
    ManyToOne,
    /// Many-to-many relationship
    ManyToMany,
}

/// Information about relationships between tables
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RelationshipInfo {
    /// Type of relationship
    pub relationship_type: RelationshipType,
    /// Source table
    pub from_table: String,
    /// Source field/column
    pub from_field: String,
    /// Target table
    pub to_table: String,
    /// Target field/column
    pub to_field: String,
    /// Junction table for many-to-many relationships
    pub junction_table: Option<String>,
}

/// GraphQL type information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GraphQLTypeInfo {
    /// GraphQL type name
    pub name: String,
    /// GraphQL type kind (OBJECT, INPUT, ENUM, etc.)
    pub kind: GraphQLTypeKind,
    /// Type description
    pub description: Option<String>,
    /// Fields for object and input types
    pub fields: Vec<GraphQLFieldInfo>,
    /// Possible values for enum types
    pub enum_values: Vec<String>,
}

/// GraphQL type kinds
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum GraphQLTypeKind {
    /// Object type
    Object,
    /// Input type
    Input,
    /// Enum type
    Enum,
    /// Scalar type
    Scalar,
    /// Union type
    Union,
    /// Interface type
    Interface,
}

/// GraphQL field information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GraphQLFieldInfo {
    /// Field name
    pub name: String,
    /// Field type
    pub field_type: String,
    /// Whether the field is required (non-null)
    pub is_required: bool,
    /// Whether the field is a list
    pub is_list: bool,
    /// Field description
    pub description: Option<String>,
    /// Default value for input fields
    pub default_value: Option<String>,
}

/// Database metadata containing all table information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseMetadata {
    /// All tables in the database
    pub tables: HashMap<String, TableInfo>,
    /// All relationships between tables
    pub relationships: Vec<RelationshipInfo>,
    /// Database version
    pub version: Option<String>,
    /// Database type (PostgreSQL, MySQL, etc.)
    pub database_type: String,
}

impl DatabaseMetadata {
    /// Create a new empty database metadata
    pub fn new(database_type: String) -> Self {
        Self {
            tables: HashMap::new(),
            relationships: Vec::new(),
            version: None,
            database_type,
        }
    }
    
    /// Add a table to the metadata
    pub fn add_table(&mut self, table: TableInfo) {
        self.tables.insert(table.name.clone(), table);
    }
    
    /// Get a table by name
    pub fn get_table(&self, name: &str) -> Option<&TableInfo> {
        self.tables.get(name)
    }
    
    /// Get all table names
    pub fn table_names(&self) -> Vec<&String> {
        self.tables.keys().collect()
    }
    
    /// Add a relationship
    pub fn add_relationship(&mut self, relationship: RelationshipInfo) {
        self.relationships.push(relationship);
    }
    
    /// Get relationships for a specific table
    pub fn get_relationships_for_table(&self, table_name: &str) -> Vec<&RelationshipInfo> {
        self.relationships
            .iter()
            .filter(|r| r.from_table == table_name || r.to_table == table_name)
            .collect()
    }
}

impl TableInfo {
    /// Create a new table info
    pub fn new(name: String, schema: String) -> Self {
        Self {
            name,
            schema,
            comment: None,
            columns: Vec::new(),
            primary_keys: Vec::new(),
            foreign_keys: Vec::new(),
        }
    }
    
    /// Add a column to the table
    pub fn add_column(&mut self, column: ColumnInfo) {
        if column.is_primary_key {
            self.primary_keys.push(column.name.clone());
        }
        self.columns.push(column);
    }
    
    /// Get a column by name
    pub fn get_column(&self, name: &str) -> Option<&ColumnInfo> {
        self.columns.iter().find(|c| c.name == name)
    }
    
    /// Get all column names
    pub fn column_names(&self) -> Vec<&String> {
        self.columns.iter().map(|c| &c.name).collect()
    }
    
    /// Check if table has a primary key
    pub fn has_primary_key(&self) -> bool {
        !self.primary_keys.is_empty()
    }
    
    /// Add a foreign key
    pub fn add_foreign_key(&mut self, foreign_key: ForeignKeyInfo) {
        self.foreign_keys.push(foreign_key);
    }
}

impl ColumnInfo {
    /// Create a new column info
    pub fn new(name: String, data_type: String) -> Self {
        Self {
            name,
            data_type,
            is_nullable: true,
            is_primary_key: false,
            is_auto_increment: false,
            default_value: None,
            comment: None,
            max_length: None,
            precision: None,
            scale: None,
        }
    }
    
    /// Set the column as primary key
    pub fn primary_key(mut self) -> Self {
        self.is_primary_key = true;
        self.is_nullable = false;
        self
    }
    
    /// Set the column as not nullable
    pub fn not_null(mut self) -> Self {
        self.is_nullable = false;
        self
    }
    
    /// Set the column as auto increment
    pub fn auto_increment(mut self) -> Self {
        self.is_auto_increment = true;
        self
    }
    
    /// Set the default value
    pub fn default_value<S: Into<String>>(mut self, value: S) -> Self {
        self.default_value = Some(value.into());
        self
    }
    
    /// Set the comment
    pub fn comment<S: Into<String>>(mut self, comment: S) -> Self {
        self.comment = Some(comment.into());
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_table_info_creation() {
        let mut table = TableInfo::new("users".to_string(), "public".to_string());
        assert_eq!(table.name, "users");
        assert_eq!(table.schema, "public");
        assert!(table.columns.is_empty());
        assert!(!table.has_primary_key());
        
        let id_column = ColumnInfo::new("id".to_string(), "integer".to_string())
            .primary_key()
            .auto_increment();
        
        table.add_column(id_column);
        assert_eq!(table.columns.len(), 1);
        assert!(table.has_primary_key());
        assert_eq!(table.primary_keys[0], "id");
    }
    
    #[test]
    fn test_column_info_builder() {
        let column = ColumnInfo::new("email".to_string(), "varchar".to_string())
            .not_null()
            .comment("User email address");
        
        assert_eq!(column.name, "email");
        assert_eq!(column.data_type, "varchar");
        assert!(!column.is_nullable);
        assert_eq!(column.comment, Some("User email address".to_string()));
    }
    
    #[test]
    fn test_database_metadata() {
        let mut metadata = DatabaseMetadata::new("PostgreSQL".to_string());
        assert_eq!(metadata.database_type, "PostgreSQL");
        assert!(metadata.tables.is_empty());
        
        let table = TableInfo::new("users".to_string(), "public".to_string());
        metadata.add_table(table);
        
        assert_eq!(metadata.tables.len(), 1);
        assert!(metadata.get_table("users").is_some());
        assert!(metadata.get_table("posts").is_none());
    }
    
    #[test]
    fn test_relationship_info() {
        let relationship = RelationshipInfo {
            relationship_type: RelationshipType::OneToMany,
            from_table: "users".to_string(),
            from_field: "id".to_string(),
            to_table: "posts".to_string(),
            to_field: "user_id".to_string(),
            junction_table: None,
        };
        
        assert_eq!(relationship.relationship_type, RelationshipType::OneToMany);
        assert_eq!(relationship.from_table, "users");
        assert_eq!(relationship.to_table, "posts");
    }
}